<?php
/**
 * @link http://www.tintsoft.com/
 * @copyright Copyright (c) 2013 TintSoft LLC
 * @license http://www.tintsoft.com/license/
 */
namespace Leaps\Base;
use Leaps;
use Leaps\Base\Container;
use Leaps\Helper\Console;
use Leaps\Web\HttpException;
use Leaps\Log\LogServiceProvider;
use Leaps\Events\EventServiceProvider;
use Leaps\Config\ConfigServiceProvider;
use Leaps\Exception\ExceptionServiceProvider;
use Leaps\Filesystem\FilesystemServiceProvider;
class Application extends Container
{
    public $_basePath;

    /**
     * 所有的注册服务提供者。
     *
     * @var array
     */
    protected $serviceProviders = array ();

    /**
     * 已经注册并加载的服务提供者
     *
     * @var array
     */
    protected $loadedProviders = array ();
    public $memoryReserveSize = 262144;
    private $_memoryReserve;

    /**
     * 创建应用程序实例
     *
     * @param \Leasps\Http\Request $request
     * @return void
     */
    public function __construct($basePath)
    {
        Leaps::$app = $this;
        $this->preInit ( $basePath );
        $this->register ( new FilesystemServiceProvider ( $this ) );
        $this->register ( new ConfigServiceProvider ( $this ) );
        $this->register ( new LogServiceProvider ( $this ) );
        $this->register ( new ExceptionServiceProvider ( $this ) );
        //$this->register ( new EventServiceProvider ( $this ) );
        //$this->registerErrorHandlers();
        $this->register ( new Leaps\Cache\CacheServiceProvider ( $this ) );
        $this ['cache']->set ( 'aaa.bbb', 'aaa' );
    }

    /**
     * 向应用程序注册一个服务提供者。
     *
     * @param \Leaps\Base\ServiceProvider|string $provider
     * @param array $options
     * @return void
     */
    public function register($provider, $options = array())
    {
        if ( is_string ( $provider ) ) {
            $provider = $this->resolveProviderClass ( $provider );
        }
        $provider->register ();
        foreach ( $options as $key => $value ) {
            $this [$key] = $value;
        }
        $this->serviceProviders [] = $provider;
        $this->loadedProviders [get_class ( $provider )] = true;
    }

    /**
     * 实例化服务提供者类
     *
     * @param string $provider
     * @return \Leaps\Base\ServiceProvider
     */
    protected function resolveProviderClass($provider)
    {
        return new $provider ( $this );
    }

    /**
     * 注册错误处理程序。
     */
    public function registerErrorHandlers()
    {
        if ( LEAPS_ENABLE_ERROR_HANDLER ) {
            ini_set ( 'display_errors', 0 );
            set_exception_handler ( [
                    $this,'handleException'
            ] );
            set_error_handler ( [
                    $this,'handleError'
            ], error_reporting () );
            if ( $this->memoryReserveSize > 0 ) {
                $this->_memoryReserve = str_repeat ( 'x', $this->memoryReserveSize );
            }
            register_shutdown_function ( [
                    $this,'handleFatalError'
            ] );
        }
    }

    /**
     * 前置初始化
     *
     * @param string $basePath
     */
    public function preInit($basePath)
    {
        if ( isset ( $basePath ) ) {
            $this->setBasePath ( $basePath );
            unset ( $basePath );
        } else {
            throw new InvalidConfigException ( 'The "basePath" configuration is required.' );
        }
        // set "@vendor"
        $this->getVendorPath ();
        // set "@runtime"
        $this->getRuntimePath ();
    }

    /**
     * 返回模块的根目录。 它包含模块类文件的默认目录。
     *
     * @return string 应用程序的根目录。
     */
    public function getBasePath()
    {
        if ( $this->_basePath === null ) {
            $class = new \ReflectionClass ( $this );
            $this->_basePath = dirname ( $class->getFileName () );
        }
        return $this->_basePath;
    }

    /**
     * 设置应用程序的根目录和@app别名。
     *
     * @param string $path 应用程序的根目录。
     * @property string 应用程序的根目录。
     * @throws InvalidParamException 如果目录不存在。
     */
    public function setBasePath($path)
    {
        $path = Leaps::getAlias ( $path );
        $p = realpath ( $path );
        if ( $p !== false && is_dir ( $p ) ) {
            $this->_basePath = $p;
        } else {
            throw new InvalidParamException ( "The directory does not exist: $path" );
        }
        Leaps::setAlias ( '@app', $this->getBasePath () );
    }
    private $_vendorPath;

    /**
     * 返回存储供应商文件的目录
     *
     * @return string the directory that stores vendor files. Defaults to
     *         "vendor" directory under [[basePath]].
     */
    public function getVendorPath()
    {
        if ( $this->_vendorPath === null ) {
            $this->setVendorPath ( $this->getBasePath () . DIRECTORY_SEPARATOR . 'vendor' );
        }
        return $this->_vendorPath;
    }

    /**
     * 设置存储供应商文件的目录。
     *
     * @param string $path the directory that stores vendor files.
     */
    public function setVendorPath($path)
    {
        $this->_vendorPath = Leaps::getAlias ( $path );
        Leaps::setAlias ( '@vendor', $this->_vendorPath );
    }

    /**
     * 获取时区 This is a simple wrapper of PHP function
     * date_default_timezone_get(). If time zone is not configured in php.ini or
     * application config, it will be set to UTC by default.
     *
     * @return string the time zone used by this application.
     * @see http://php.net/manual/en/function.date-default-timezone-get.php
     */
    public function getTimeZone()
    {
        return date_default_timezone_get ();
    }

    /**
     * 设置时区 This is a simple wrapper of PHP function
     * date_default_timezone_set().
     *
     * @param string $value the time zone used by this application.
     * @see http://php.net/manual/en/function.date-default-timezone-set.php
     */
    public function setTimeZone($value)
    {
        date_default_timezone_set ( $value );
    }
    private $_runtimePath;

    /**
     * 返回存储运行时文件的目录。
     *
     * @return string the directory that stores runtime files. Defaults to the
     *         "runtime" subdirectory under [[basePath]].
     */
    public function getRuntimePath()
    {
        if ( $this->_runtimePath === null ) {
            $this->setRuntimePath ( $this->getBasePath () . DIRECTORY_SEPARATOR . 'runtime' );
        }
        return $this->_runtimePath;
    }

    /**
     * 设置存储运行时文件的目录。
     *
     * @param string $path the directory that stores runtime files.
     */
    public function setRuntimePath($path)
    {
        $this->_runtimePath = Leaps::getAlias ( $path );
        Leaps::setAlias ( '@runtime', $this->_runtimePath );
    }

    /**
     * PHP未捕获异常处理。
     * 这种方法被实现为一个PHP异常处理程序。
     *
     * @param \Exception $exception the exception that is not caught
     */
    public function handleException($exception)
    {
        // disable error capturing to avoid recursive errors while handling exceptions
        restore_error_handler ();
        restore_exception_handler ();
        try {
            $this->logException ( $exception );
            if ( ($handler = $this ['exception']) !== null ) {
                $handler->handle ( $exception );
            } else {
                echo $this->renderException ( $exception );
            }
        }
        catch ( \Exception $e ) {
            // exception could be thrown in ErrorHandler::handle()
            $msg = ( string ) $e;
            $msg .= "\nPrevious exception:\n";
            $msg .= ( string ) $exception;
            if ( LEAPS_DEBUG ) {
                if ( PHP_SAPI === 'cli' ) {
                    echo $msg . "\n";
                } else {
                    echo '<pre>' . htmlspecialchars ( $msg, ENT_QUOTES, $this ['config'] ['app.charset'] ) . '</pre>';
                }
            }
            $msg .= "\n\$_SERVER = " . var_export ( $_SERVER, true );
            error_log ( $msg );
            exit ( 1 );
        }
    }

    /**
     * 处理PHP执行错误,如警告、通知。
     * 此方法作为一个PHP错误处理程序。它只会增加一个“ErrorException”。
     *
     * @param integer $code the level of the error raised
     * @param string $message the error message
     * @param string $file the filename that the error was raised in
     * @param integer $line the line number the error was raised at
     * @throws ErrorException
     */
    public function handleError($code, $message, $file, $line)
    {
        if ( error_reporting () !== 0 ) {
            // load ErrorException manually here because autoloading them will not work
            // when error occurs while autoloading a class
            if ( ! class_exists ( '\\Leaps\\Base\\Exception', false ) ) {
                require_once (__DIR__ . '/Exception.php');
            }
            if ( ! class_exists ( '\\Leaps\\Base\\ErrorException', false ) ) {
                require_once (__DIR__ . '/ErrorException.php');
            }
            $exception = new ErrorException ( $message, $code, $code, $file, $line );
            // in case error appeared in __toString method we can't throw any exception
            $trace = debug_backtrace ( false );
            array_shift ( $trace );
            foreach ( $trace as $frame ) {
                if ( $frame ['function'] == '__toString' ) {
                    $this->handleException ( $exception );
                    exit ( 1 );
                }
            }
            throw $exception;
        }
    }

    /**
     * PHP致命错误处理
     */
    public function handleFatalError()
    {
        unset ( $this->_memoryReserve );
        // load ErrorException manually here because autoloading them will not work
        // when error occurs while autoloading a class
        if ( ! class_exists ( '\\Leaps\\Base\\Exception', false ) ) {
            require_once (__DIR__ . '/Exception.php');
        }
        if ( ! class_exists ( '\\Leaps\\Base\\ErrorException', false ) ) {
            require_once (__DIR__ . '/ErrorException.php');
        }
        $error = error_get_last ();
        if ( ErrorException::isFatalError ( $error ) ) {
            $exception = new ErrorException ( $error ['message'], $error ['type'], $error ['type'], $error ['file'], $error ['line'] );
            // 使用error_log因为太晚了无法使用Leaps日志
            error_log ( $exception );
            if ( ($handler = $this ['exception']) !== null ) {
                $handler->handle ( $exception );
            } else {
                echo $this->renderException ( $exception );
            }
            exit ( 1 );
        }
    }

    /**
     * 不使用丰富的格式呈现异常。
     *
     * @param \Exception $exception the exception to be rendered.
     * @return string the rendering result
     */
    public function renderException($exception)
    {
        if ( $exception instanceof Exception && ($exception instanceof UserException || ! LEAPS_DEBUG) ) {
            $message = $exception->getName () . ': ' . $exception->getMessage ();
            if ( Leaps::$app->controller instanceof \Leaps\Console\Controller ) {
                $message = Leaps::$app->controller->ansiFormat ( $message, Console::FG_RED );
            }
        } else {
            $message = LEAPS_DEBUG ? ( string ) $exception : 'Error: ' . $exception->getMessage ();
        }
        if ( PHP_SAPI === 'cli' ) {
            return $message . "\n";
        } else {
            return '<pre>' . htmlspecialchars ( $message, ENT_QUOTES, $this ['config'] ['app.charset'] ) . '</pre>';
        }
    }

    /**
     * 记录异常日志
     *
     * @param \Exception $exception the exception to be logged
     */
    protected function logException($exception)
    {
        $category = get_class ( $exception );
        if ( $exception instanceof HttpException ) {
            $category .= '\\' . $exception->statusCode;
        } elseif ( $exception instanceof \ErrorException ) {
            $category .= '\\' . $exception->getSeverity ();
        }
        Leaps::error ( ( string ) $exception, $category );
    }
}